package com.blue.jdbc.annotation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blue.core.util.ReflectionUtil;
import com.blue.jdbc.sql.SqlUtil;

/**
 * 对象解析类
 * 
 * @author zhengj
 * @since 2016年7月9日 1.0
 */
public class Parser
{
	private static Logger log = LoggerFactory.getLogger(Parser.class);
	
	private SqlUtil sqlUtil;
	
	private Map<Class<?>, CacheEntity> cache = new HashMap<>();
	
	private static Parser instance = new Parser();

	private Parser()
	{
	}
	
	public static Parser getInstance()
	{
		return instance;
	}
	
	/**
	 * 解析
	 * 
	 * @param clazz
	 * @param debug
	 * @return
	 */
	public CacheEntity parse(Class<?> clazz)
	{
		/*CacheEntity entity = cache.get(clazz);
		if (entity != null)
			return entity;
		
		entity = this.paserEntity(clazz);
		sqlUtil.generateSQL(entity);
		cache.put(clazz, entity);
		return entity;*/
		
		return cache.computeIfAbsent(clazz, c ->
		{
			CacheEntity entity = this.paserEntity(clazz);
			sqlUtil.generateSQL(entity);
			return entity;
		});
		
	}

	/**
	 * 解析实体
	 * 
	 * @param clazz
	 * @return
	 */
	private CacheEntity paserEntity(Class<?> clazz)
	{
		Table annoTable = clazz.getAnnotation(Table.class);
		if (annoTable == null)
			throw new IllegalArgumentException(clazz + " 缺少 @Table 注解");
		
		CacheEntity cacheEntity = new CacheEntity();
		cacheEntity.setClazz(clazz);
		
		String table = (annoTable.name().isEmpty() ? ReflectionUtil.fieldToColumn(clazz.getSimpleName()) : annoTable.name());
		cacheEntity.setTable(table);
		
		log.info("mapped class: " + clazz.getName() + " <==> " + table);
		
		this.parseField(cacheEntity, clazz);
		
		return cacheEntity;
	}
	
	/**
	 * 解析字段
	 * 
	 * @param cacheEntity
	 * @param clazz
	 */
	private  void parseField(CacheEntity cacheEntity, Class<?> clazz)
	{
		Map<String, Field> fieldMap = new HashMap<>();
		Map<String, Method> setterMap = new HashMap<>();
		Map<String, Method> getterMap = new HashMap<>();
		
		this.initMember(clazz, fieldMap, setterMap, getterMap);
		
		for (Map.Entry<String, Method> entry : setterMap.entrySet())
		{
			String fieldName = entry.getKey();
			Field field = fieldMap.get(fieldName);
			if (field != null) // 类本身字段
			{
				Id annoId = field.getAnnotation(Id.class);
				
				if (annoId != null) // 是主键
				{
					this.parseFieldId(field, annoId, setterMap, getterMap, cacheEntity);
					continue;
				}
				
				Version annoVersion = field.getAnnotation(Version.class);
				if (annoVersion != null) // 版本
				{
					this.parseFieldVersion(field, annoVersion, setterMap, getterMap, cacheEntity);
					continue;
				}
				
				// 字段
				this.parseFieldColumn(field, setterMap, getterMap, cacheEntity);
			}
			else // 继承额外字段
			{
				this.parseFieldExtra(fieldName, setterMap, getterMap, cacheEntity);
			}
		}
		
	}

	private void initMember(Class<?> clazz, Map<String, Field> fieldMap,
			Map<String, Method> setterMap, Map<String, Method> getterMap)
	{
		for (Field field : clazz.getDeclaredFields())
		{
			fieldMap.put(field.getName(), field);
		}
		for (Method setter : ReflectionUtil.setterList(clazz))
		{
			setterMap.put(ReflectionUtil.field(setter), setter);
		}
		for (Method getter : ReflectionUtil.getterList(clazz))
		{
			getterMap.put(ReflectionUtil.field(getter), getter);
		}
	}

	private void parseFieldId(Field field, Id annoId, Map<String, Method> setterMap,
			Map<String, Method> getterMap, CacheEntity cacheEntity)
	{
		Column annoColumn = field.getAnnotation(Column.class);
		String column = (annoColumn == null ? ReflectionUtil.fieldToColumn(field.getName()) : annoColumn.name());
		
		Method setter = setterMap.get(field.getName());
		if (setter == null)
			throw new IllegalArgumentException(field.getName() + " 不存在 setter 方法");

		Method getter = getterMap.get(field.getName());
		if (getter == null)
			throw new IllegalArgumentException(field.getName() + " 不存在 getter 方法");
		
		CacheId cacheId = new CacheId();
		cacheId.setField(field);
		cacheId.setName(field.getName());
		cacheId.setSetterMethod(setter);
		cacheId.setGetterMethod(getter);
		cacheId.setColumn(column);
		cacheId.setIdType(field.getType() == String.class ? IdType.STRING : IdType.INT);
		
		GeneratorType type = annoId.generator();
		if (type == GeneratorType.AUTO)
		{
			if (cacheId.getIdType() == IdType.STRING)
				cacheId.setGeneratorType(GeneratorType.UUID);
			else
				cacheId.setGeneratorType(GeneratorType.INCREMENT);
		}
		else
		{
			cacheId.setGeneratorType(type);
		}
		
		log.debug("id: {} <==> {}", field.getName(), column);
		cacheEntity.putId(cacheId);
	}
	
	private void parseFieldVersion(Field field, Version annoVersion, Map<String, Method> setterMap,
			Map<String, Method> getterMap, CacheEntity cacheEntity)
	{
		Column annoColumn = field.getAnnotation(Column.class);
		String column = (annoColumn == null ? ReflectionUtil.fieldToColumn(field.getName()) : annoColumn.name());
		
		if (field.getType() != int.class && field.getType() != Integer.class
				&& field.getType() != long.class && field.getType() != Long.class)
			throw new IllegalArgumentException(field.getName() + " 必须是 int 或 long 类型");
		
		Method setter = setterMap.get(field.getName());
		if (setter == null)
			throw new IllegalArgumentException(field.getName() + " 不存在 setter 方法");

		Method getter = getterMap.get(field.getName());
		if (getter == null)
			throw new IllegalArgumentException(field.getName() + " 不存在 getter 方法");
		
		if (cacheEntity.getVersion() != null)
			throw new IllegalArgumentException(cacheEntity.getClazz().getName() + " 最多只能有一个 @Version");
		
		CacheVersion cacheVersion = new CacheVersion();
		cacheVersion.setField(field);
		cacheVersion.setGetterMethod(getter);
		cacheVersion.setSetterMethod(setter);
		cacheVersion.setName(field.getName());
		cacheVersion.setColumn(column);
		cacheVersion.setForce(annoVersion.force());

		log.debug("version: {} <==> {}", field.getName(), column);
		cacheEntity.setVersion(cacheVersion);
	}

	private void parseFieldColumn(Field field, Map<String, Method> setterMap,
			Map<String, Method> getterMap, CacheEntity cacheEntity)
	{
		Column annoColumn = field.getAnnotation(Column.class);
		String column = (annoColumn == null ? ReflectionUtil.fieldToColumn(field.getName()) : annoColumn.name());
		
		Method setter = setterMap.get(field.getName());
		if (setter == null)
			throw new IllegalArgumentException(field.getName() + " 不存在 setter 方法");

		Method getter = getterMap.get(field.getName());
		if (getter == null)
			throw new IllegalArgumentException(field.getName() + " 不存在 getter 方法");
		
		CacheColumn cacheColumn = new CacheColumn();
		cacheColumn.setField(field);
		cacheColumn.setGetterMethod(getter);
		cacheColumn.setSetterMethod(setter);
		cacheColumn.setName(field.getName());
		cacheColumn.setColumn(column);
		
		Must annoMust = field.getAnnotation(Must.class);
		if (annoMust != null)
		{
			cacheColumn.setMustInsert(annoMust.insert());
			cacheColumn.setMustUpdate(annoMust.update());
		}
		
		if (field.getAnnotation(Transient.class) == null)
		{
			log.debug("column: {} <==> {}", field.getName(), column);
			cacheEntity.putColumn(cacheColumn);
		}
		else
		{
			log.debug("extra: {} <==> {}", field.getName(), column);
			cacheEntity.putExtra(cacheColumn);
		}
		
		
	}

	private void parseFieldExtra(String fieldName, Map<String, Method> setterMap,
			Map<String, Method> getterMap, CacheEntity cacheEntity)
	{
		Method setter = setterMap.get(fieldName);
		if (setter == null)
			throw new IllegalArgumentException(fieldName + " 不存在 setter 方法");

		Method getter = getterMap.get(fieldName);
		if (getter == null)
			throw new IllegalArgumentException(fieldName + " 不存在 getter 方法");

		CacheColumn cacheColumn = new CacheColumn();
		cacheColumn.setGetterMethod(getter);
		cacheColumn.setSetterMethod(setter);
		cacheColumn.setName(fieldName);
		cacheColumn.setColumn(ReflectionUtil.fieldToColumn(fieldName));
		log.debug("extra: {} <==> {}", fieldName, cacheColumn.getColumn());
		cacheEntity.putExtra(cacheColumn);
	}

	public void setSqlUtil(SqlUtil sqlUtil)
	{
		this.sqlUtil = sqlUtil;
	}
	
}
